#include "convert.h"
#include "ui_convert.h"

QString sourcePath = "";
QString targetPath = "";
bool checkIsBilibiliVideo(QString path);
QStringList searchBilibiliVideo(QString path);
VideoInfo* resolveVideoInfo(QString sourcePath);
QString genrateUuid();
void decodeM4s(QString inputPath, QString outputPath);
VideoInfo* resolvePCVideo(QString sourcePath);
VideoInfo* resolveMobileVideo(QString sourcePath);
QString replaceSpeciChar(QString input);
int doubleToInt(double d);

int totalCnt = 0;
int finishCnt = 0;
qint64 currentFileSize = 0;

Convert::Convert(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Convert)
{
    ui->setupUi(this);
    this->setWindowIcon(QIcon(":/image/logo.ico"));
    QDir dir(".\\.temp");
    dir.removeRecursively();
    dir.mkpath(".\\.temp");
    auto *worker = new ExecWorker;
    // 调用 moveToThread 将该任务交给 workThread
    worker->moveToThread(&workerThread);
    // operate 信号发射后启动线程工作
    connect(this, SIGNAL(operate(QStringList, QString)), worker, SLOT(doWork(QStringList, QString)));
    // 该线程结束时销毁
    connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
    // 线程结束后发送信号，对结果进行处理
    connect(worker, SIGNAL(onFinish(QString, QString)), this, SLOT(handleResults(QString, QString)));
    // 启动线程
    workerThread.start();

    //处理进度条
    connect(ui->ffmpegProgress, SIGNAL(inProcess(qint64)), this, SLOT(handleFFmpegProcess(qint64)));
    connect(ui->ffmpegProgress, SIGNAL(onFinish()), this, SLOT(handleFFmpegFinish()));
}

Convert::~Convert()
{
    delete ui;
    workerThread.quit();
    workerThread.wait();
}

void Convert::on_sourcePathBtn_pressed()
{
    QString path = QFileDialog::getExistingDirectory(this, "缓存视频源文件路径", "./", QFileDialog::ShowDirsOnly);
    sourcePath = path;
    ui->sourcePathText->setText(sourcePath);
}

void Convert::on_targetPathBtn_pressed()
{
    QString path = QFileDialog::getExistingDirectory(this, "MP4输出路径", "./", QFileDialog::ShowDirsOnly);
    targetPath = path;
    ui->targetPathText->setText(targetPath);
}

void Convert::on_convertBtn_pressed()
{
    if(!checkInput())
    {
        return;
    }
    m_pTimer = new QTimer(this);
    connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
    m_pTimer->start(TIMER_TIMEOUT);
    ui->convertBtn->setDisabled(true);
    totalCnt = 0;
    finishCnt = 0;
    ui->log->append("源路径：" + ui->sourcePathText->text());
    ui->log->append("目标路径：" + ui->targetPathText->text());
    QStringList videoPath = searchBilibiliVideo(ui->sourcePathText->text());
    ui->log->append("找到视频数量：" + QString::number(videoPath.size()));
    QStringListIterator strIterator(videoPath);
    while (strIterator.hasNext()) {
        convertBilibiliVideoToMp4(strIterator.next(), ui->targetPathText->text());
    }
    totalCnt = videoPath.size();
    ui->totalCnt->setText(QString::number(totalCnt));
}

void Convert::on_clearLog_pressed()
{
    ui->log->clear();
}

/**
 * @brief searchBilibiliVideo 搜索文件夹下的bilibili视频并返回文件夹路径
 * @param path 文件夹
 * @return QStringList
 */
QStringList searchBilibiliVideo(QString path)
{
    QStringList res;
    QDir dir(path);
    if(!dir.exists())
    {
        return res;
    }
    QStringList filters;
    filters << "entry.json" << ".videoInfo";
    QDirIterator dir_iterator(path, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
    while(dir_iterator.hasNext())
    {
        dir_iterator.next();
        QFileInfo fileInfo = dir_iterator.fileInfo();
        QString absolutePath = fileInfo.absolutePath();
        if(checkIsBilibiliVideo(absolutePath))
        {
            res.append(absolutePath);
        }
    }
    return res;
}

/**
 * @brief checkIsBilibiliVideo 判断文件夹下[entry.json,video.m4s,audio.m4s,.videoInfo]文件是否存在
 * @param path 文件夹
 * @return bool
 */
bool checkIsBilibiliVideo(QString path)
{
    QDir dir(path);
    if(!dir.exists())
    {
        return false;
    }
    QStringList filters;
    filters << "entry.json" << "*.m4s" << ".videoInfo";
    QDirIterator dir_iterator(path, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
    QStringList string_list;
    while(dir_iterator.hasNext())
    {
        dir_iterator.next();
        QFileInfo fileInfo = dir_iterator.fileInfo();
        QString absoluteFilePath = fileInfo.absoluteFilePath();
        string_list.append(absoluteFilePath);
    }
    if(string_list.size() == 3) {
        return true;
    }
    return false;
}

void Convert::convertBilibiliVideoToMp4(QString sourcePath, QString targetPath)
{
    /** 视频信息 */
    VideoInfo* info = resolveVideoInfo(sourcePath);
    //计算总大小
    currentFileSize = 0;
    QFile s1(info->videoFilePath);
    currentFileSize += s1.size();
    QFile s2(info->audioFilePath);
    currentFileSize += s2.size();

    /** process进度文件的路径 */
    QDir f1(".");
    QString progressPath = f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".progress");
    QFile proFile(progressPath);
    proFile.open(QIODevice::WriteOnly);
    proFile.write("");
    proFile.flush();
    proFile.close();

    ui->ffmpegProgress->setPersent(0);
    ui->ffmpegProgress->setInterval(10);
    ui->ffmpegProgress->updateFilePath(progressPath);
    QString outputPathStr = targetPath + "\\" + info->title.trimmed() + "\\";
    QDir *outputPath = new QDir;
    if(!(outputPath->exists(outputPathStr)))
    {
        outputPath->mkpath(outputPathStr);
    }
    ui->log->append("添加任务【" + info->title + "-" + info->page + "-" + info->part + "......】");
    /** 拼接命令 */
    QStringList cmdList;
    cmdList << "ffmpeg";
    cmdList << "-i";
    cmdList << info->videoFilePath;
    cmdList << "-i";
    cmdList << info->audioFilePath;
    cmdList << "-progress";
    cmdList << progressPath;
    cmdList << "-codec";
    cmdList << "copy";
    cmdList << outputPathStr + QString::number(info->page) + "-" + info->part.trimmed() + ".mp4";
    cmdList << "-y";
    operate(cmdList, info->title.trimmed() + "\\" + QString::number(info->page) + "-" + info->part.trimmed() + ".mp4");
}

/**
 * @brief resolveVideoInfo 将文件夹中的视频信息提取出来
 * @param sourcePath 视频源路径
 * @return 视频信息
 */
VideoInfo* resolveVideoInfo(QString sourcePath)
{
    /** 判断是手机版的缓存还是PC版的缓存 */
    bool isMobile = false;
    QStringList filters;
    filters << "entry.json" << ".videoInfo";
    QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
    QStringList string_list;
    while(dir_iterator.hasNext())
    {
        dir_iterator.next();
        QFileInfo fileInfo = dir_iterator.fileInfo();
        QString absoluteFilePath = fileInfo.absoluteFilePath();
        string_list.append(absoluteFilePath);
    }
    QStringListIterator strIterator(string_list);
    while (strIterator.hasNext()) {
        QString temp = strIterator.next();
        if(temp.contains("entry.json")){
            isMobile = true;
        }
    }
    if(isMobile){
        return resolveMobileVideo(sourcePath);
    }else {
        return resolvePCVideo(sourcePath);
    }
}

VideoInfo* resolveMobileVideo(QString sourcePath)
{
    /** 获取m4s和json文件的路径 */
    QDir dir(sourcePath);
    if(!dir.exists())
    {
        throw -1;
    }
    QStringList filters;
    filters << "entry.json" << "video.m4s" << "audio.m4s";
    QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
    QStringList string_list;
    while(dir_iterator.hasNext())
    {
        dir_iterator.next();
        QFileInfo fileInfo = dir_iterator.fileInfo();
        QString absoluteFilePath = fileInfo.absoluteFilePath();
        string_list.append(absoluteFilePath);
    }
    QString jsonFilePath = "";
    QString videoFilePath = "";
    QString audioFilePath = "";
    QStringListIterator strIterator(string_list);
    while (strIterator.hasNext()) {
        QString temp = strIterator.next();
        if(temp.contains("entry.json")){
            jsonFilePath = temp;
        }else if(temp.contains("video.m4s")){
            videoFilePath = temp;
        }else if(temp.contains("audio.m4s")){
            audioFilePath = temp;
        }
    }

    /** 解析json */
    QFile file(jsonFilePath);
    file.open(QIODevice::ReadOnly | QIODevice::Text);
    QString value = file.readAll();
    file.close();
    QJsonParseError parseJsonErr;
    QJsonDocument document = QJsonDocument::fromJson(value.toUtf8(), &parseJsonErr);
    if (! (parseJsonErr.error == QJsonParseError::NoError)) {
        QMessageBox::about(NULL, "提示", "配置文件错误！");
        throw -1;
    }
    QJsonObject jsonObject = document.object();
    QString title = jsonObject["title"].toString();
    QString quality = jsonObject["quality_pithy_description"].toString();
    // 普通视频拥有该字段
    QJsonValue pageData = jsonObject.value("page_data");
    int page = 0;
    QString part = "";
    if(!pageData.isUndefined()) {
        QJsonObject pageDataObj =  pageData.toObject();
        page = pageDataObj["page"].toInt();
        part = pageDataObj["part"].toString();
    }else {
        // 番剧视频
        pageData = jsonObject.value("ep");
        if(!pageData.isUndefined()) {
            QJsonObject pageDataObj =  pageData.toObject();
            page = pageDataObj["index"].toString().toInt();
            part = pageDataObj["index_title"].toString();
        }
    }

    if(pageData.isUndefined()) {
        throw -1;
    }

    /** 组装信息 */
    VideoInfo* temp = new VideoInfo;
    temp->title = replaceSpeciChar(title);
    temp->part = replaceSpeciChar(part);
    temp->page = page;
    temp->audioFilePath = audioFilePath;
    temp->videoFilePath = videoFilePath;
    return temp;
}

/**
 * @brief replaceSpeciChar 替换掉window文件夹中不应出现的特殊字符
 * @param input 输入字符
 * @return 替换后的字符
 */
QString replaceSpeciChar(QString input)
{
    input = input.replace("|","");
    input = input.replace(">","");
    input = input.replace("<","");
    input = input.replace("\"","");
    input = input.replace("?","");
    input = input.replace("*","");
    input = input.replace(":","");
    input = input.replace("/","");
    input = input.replace("\\","");
    input = input.replace("&","");
    input = input.replace('\a',"");
    input = input.replace('\b',"");
    input = input.replace('\f',"");
    input = input.replace('\n',"");
    input = input.replace('\r',"");
    input = input.replace('\t',"");
    input = input.replace('\v',"");
    return input;
}

VideoInfo* resolvePCVideo(QString sourcePath)
{
    /** 获取m4s和json文件的路径 */
    QDir dir(sourcePath);
    if(!dir.exists())
    {
        throw -1;
    }
    QStringList filters;
    filters << ".videoInfo" << "*.m4s";
    QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
    QStringList string_list;
    while(dir_iterator.hasNext())
    {
        dir_iterator.next();
        QFileInfo fileInfo = dir_iterator.fileInfo();
        QString absoluteFilePath = fileInfo.absoluteFilePath();
        string_list.append(absoluteFilePath);
    }
    QString jsonFilePath = "";
    QStringList m4sPathList;
    QStringListIterator strIterator(string_list);
    while (strIterator.hasNext()) {
        QString temp = strIterator.next();
        if(temp.contains(".videoInfo")){
            jsonFilePath = temp;
        }else if(temp.contains(".m4s")){
            m4sPathList << temp;
        }
    }

    /** 解析json */
    QFile file(jsonFilePath);
    file.open(QIODevice::ReadOnly | QIODevice::Text);
    QString value = file.readAll();
    file.close();
    QJsonParseError parseJsonErr;
    QJsonDocument document = QJsonDocument::fromJson(value.toUtf8(), &parseJsonErr);
    if (! (parseJsonErr.error == QJsonParseError::NoError)) {
        QMessageBox::about(NULL, "提示", "配置文件错误！");
        throw -1;
    }
    QJsonObject jsonObject = document.object();
    QString title = jsonObject["groupTitle"].toString();
    QString part = jsonObject["title"].toString();
    int page = jsonObject["p"].toInt();

    /** 解密m4s文件 */
    QStringList decodedM4sList;
    QDir f1(".");
    decodedM4sList << f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".m4s");
    decodedM4sList << f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".m4s");
    decodeM4s(m4sPathList.at(0), decodedM4sList.at(0));
    decodeM4s(m4sPathList.at(1), decodedM4sList.at(1));

    /** 组装信息 */
    VideoInfo* temp = new VideoInfo;
    temp->title = replaceSpeciChar(title);
    temp->part = replaceSpeciChar(part);
    temp->page = page;
    temp->audioFilePath = decodedM4sList.at(0);
    temp->videoFilePath = decodedM4sList.at(1);
    return temp;
}

/**
 * @brief decodeM4s 解密b站m4s缓存视频
 * @param inputPath 文件所在路径
 * @param outputPath 解密文件保存路径
 */
void decodeM4s(QString inputPath, QString outputPath)
{
    QFile fin(inputPath);
    fin.open(QIODevice::ReadOnly);
    QFile fout(outputPath);
    fout.open(QIODevice::WriteOnly);
    qint64 r;
    bool flag = true;
    do {
        uchar buffer[1024];
        r = fin.read((char*)buffer,sizeof(buffer));
        if (r<0){
            throw 1;
        }
        if (r<=0)
            break;
        QByteArray array((char*)buffer, r);
        if(flag)
        {
            flag = false;
            array.remove(0, 9);
            array[3] = 0x20;
            if(!(array[16] == 0x69 && array[17] == 0x73 && array[18] == 0x6f && array[19] == 0x35)) {
                array.remove(16, 4);
            }
        }
        fout.write(array);
    } while (r>0);
    fin.close();
    fout.close();
}

/**
 * @brief genrateUuid 生成不重复的文件名字
 * @return
 */
QString genrateUuid()
{
    QUuid id = QUuid::createUuid();
    QString strId = id.toString();
    QString timestamp = QString::number(QDateTime::currentMSecsSinceEpoch());
    return timestamp+strId.remove("{").remove("}").remove("-");
}

/**
 * @brief Convert::handleResults 处理命令行返回结果
 * @param result ffmpeg返回
 * @param metaStr 元数据
 */
void Convert::handleResults(QString result, QString metaStr)
{
//    qDebug() << result;
    ui->log->append("保存到视频【" + metaStr + "】");
    finishCnt++;
    ui->finishCnt->setText(QString::number(finishCnt));
}

void Convert::handleTimeout()
{
    ui->convertBtn->setDisabled(false);
    m_pTimer->stop();
}

/**
 * @brief checkInput 检测输入是否合法
 */
bool Convert::checkInput()
{
    if(ui->sourcePathText->text().isEmpty() || ui->targetPathText->text().isEmpty())
    {
        QMessageBox::warning(NULL, "提示", "亲，请先选择路径，不然会报错的！");
        return false;
    }
    return true;
}

void Convert::handleMouseReleaseEvent()
{
    QDesktopServices::openUrl(QUrl(QString("https://www.bilibili.com/video/BV17M411h714/?share_source=copy_web&vd_source=ecf24c957991c7b438afdd43d0d3b652")));
}

/**
 * @brief Convert::handleFFmpegProcess 处理FFmpegProgress的进度
 * @param p 进度值
 */
void Convert::handleFFmpegProcess(qint64 p)
{
    double a = (double)p / (double)currentFileSize;
    a = a * 100;
    int value  = doubleToInt(a);
    if(value >= 100)
    {
        value = 99;
    }
    ui->ffmpegProgress->setPersent(value);
}

/**
 * @brief Convert::handleFFmpegFinish 处理FFmpegProgress的完成信号
 */
void Convert::handleFFmpegFinish()
{
    ui->ffmpegProgress->stopProcess();
    ui->ffmpegProgress->setPersent(100);
}

/**
 * @brief doubleToInt 将double转为int
 * @param d
 * @return
 */
int doubleToInt(double d)
{
    double intPart = floor(d);
    if ((d - intPart) >= (double)0.5)
    {
        return (intPart + 1);
    }
    else
    {
        return intPart;
    }
}

void Convert::on_btnBilibili_pressed()
{
    QDesktopServices::openUrl(QUrl(QString("https://www.bilibili.com/video/BV17M411h714/?share_source=copy_web&vd_source=ecf24c957991c7b438afdd43d0d3b652")));
}
